package jannex.sql.extensions;

import jannex.Jannex;
import jannex.internal.ExceptionFactory;
import jannex.internal.ReflectionUtils;
import jannex.internal.SqlTypeUtils;
import jannex.mapping.ResultSetHandler;
import jannex.sql.JxArray;
import jannex.sql.JxParameterMetaData;
import jannex.sql.JxPreparedStatement;
import jannex.sql.JxResultSet;
import jannex.sql.JxResultSetMetaData;
import jannex.sql.TypedPreparedStatement;

import java.io.InputStream;
import java.io.Reader;
import java.math.BigDecimal;
import java.net.URL;
import java.sql.*;
import java.util.Calendar;
import java.util.Collection;

public class PreparedStatementImpl extends StatementImpl implements JxPreparedStatement {

    protected final PreparedStatement preparedStatement;

    public PreparedStatementImpl(PreparedStatement preparedStatement, Jannex jannex) {
        super(preparedStatement, jannex);
        this.preparedStatement = preparedStatement;
    }

    @Override
    public <E> TypedPreparedStatement<E> as(ResultSetHandler<E> handler) {
        return new TypedPreparedStatementImpl<>(preparedStatement, jannex, handler);
    }
    @Override
    public <E> TypedPreparedStatement<E> as(Class<E> elementClass) {
        return as(jannex.getHandler(elementClass));
    }

    @Override
    public JxResultSet executeQuery() {
        try {
            return new ResultSetImpl(preparedStatement.executeQuery(), jannex);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public int executeUpdate() {
        try {
            return preparedStatement.executeUpdate();
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setNull(int parameterIndex, int sqlType) {
        try {
            preparedStatement.setNull(parameterIndex, sqlType);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setBoolean(int parameterIndex, boolean x) {
        try {
            preparedStatement.setBoolean(parameterIndex, x);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setByte(int parameterIndex, byte x) {
        try {
            preparedStatement.setByte(parameterIndex, x);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setShort(int parameterIndex, short x) {
        try {
            preparedStatement.setShort(parameterIndex, x);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setInt(int parameterIndex, int x) {
        try {
            preparedStatement.setInt(parameterIndex, x);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setLong(int parameterIndex, long x) {
        try {
            preparedStatement.setLong(parameterIndex, x);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setFloat(int parameterIndex, float x) {
        try {
            preparedStatement.setFloat(parameterIndex, x);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setDouble(int parameterIndex, double x) {
        try {
            preparedStatement.setDouble(parameterIndex, x);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setBigDecimal(int parameterIndex, BigDecimal x) {
        try {
            preparedStatement.setBigDecimal(parameterIndex, x);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setString(int parameterIndex, String x) {
        try {
            preparedStatement.setString(parameterIndex, x);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setBytes(int parameterIndex, byte[] x) {
        try {
            preparedStatement.setBytes(parameterIndex, x);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setDate(int parameterIndex, Date x) {
        try {
            preparedStatement.setDate(parameterIndex, x);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setTime(int parameterIndex, Time x) {
        try {
            preparedStatement.setTime(parameterIndex, x);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setTimestamp(int parameterIndex, Timestamp x) {
        try {
            preparedStatement.setTimestamp(parameterIndex, x);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setAsciiStream(int parameterIndex, InputStream x, int length) {
        try {
            preparedStatement.setAsciiStream(parameterIndex, x, length);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    @Deprecated
    @SuppressWarnings("deprecation")
    public void setUnicodeStream(int parameterIndex, InputStream x, int length) {
        try {
            preparedStatement.setUnicodeStream(parameterIndex, x, length);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setBinaryStream(int parameterIndex, InputStream x, int length) {
        try {
            preparedStatement.setBinaryStream(parameterIndex, x, length);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void clearParameters() {
        try {
            preparedStatement.clearParameters();
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setObject(int parameterIndex, Object x, int targetSqlType) {
        try {
            preparedStatement.setObject(parameterIndex, x, targetSqlType);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setObject(int parameterIndex, Object x) {
        try {
            if (x == null) {
                preparedStatement.setObject(parameterIndex, null);
            } else if (x.getClass().isArray()) {
                JxArray array = null;
                try {
                    array = getConnection().createArrayOf(
                            SqlTypeUtils.nameOfType(x.getClass().getComponentType()),
                            ReflectionUtils.castArray(x));
                    setArray(parameterIndex, array);
                } finally {
                    if (array != null) {
                        array.free();
                    }
                }
            } else if (x instanceof Collection) {
                @SuppressWarnings("unchecked")
                Collection<Object> collection = (Collection<Object>) x;
                Object[] objects = collection.toArray();
                setObject(parameterIndex, objects);
            } else {
                preparedStatement.setObject(parameterIndex, x);
            }
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public boolean execute() {
        try {
            return preparedStatement.execute();
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void addBatch() {
        try {
            preparedStatement.addBatch();
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setCharacterStream(int parameterIndex, Reader reader, int length) {
        try {
            preparedStatement.setCharacterStream(parameterIndex, reader, length);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setRef(int parameterIndex, Ref x) {
        try {
            preparedStatement.setRef(parameterIndex, x);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setBlob(int parameterIndex, Blob x) {
        try {
            preparedStatement.setBlob(parameterIndex, x);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setClob(int parameterIndex, Clob x) {
        try {
            preparedStatement.setClob(parameterIndex, x);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setArray(int parameterIndex, Array x) {
        try {
            preparedStatement.setArray(parameterIndex, x);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public JxResultSetMetaData getMetaData() {
        try {
            return new ResultSetMetaDataImpl(preparedStatement.getMetaData(), jannex);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setDate(int parameterIndex, Date x, Calendar cal) {
        try {
            preparedStatement.setDate(parameterIndex, x, cal);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setTime(int parameterIndex, Time x, Calendar cal) {
        try {
            preparedStatement.setTime(parameterIndex, x, cal);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) {
        try {
            preparedStatement.setTimestamp(parameterIndex, x, cal);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setNull(int parameterIndex, int sqlType, String typeName) {
        try {
            preparedStatement.setNull(parameterIndex, sqlType, typeName);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setURL(int parameterIndex, URL x) {
        try {
            preparedStatement.setURL(parameterIndex, x);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public JxParameterMetaData getParameterMetaData() {
        try {
            return new ParameterMetaDataImpl(preparedStatement.getParameterMetaData(), jannex);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setRowId(int parameterIndex, RowId x) {
        try {
            preparedStatement.setRowId(parameterIndex, x);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setNString(int parameterIndex, String value) {
        try {
            preparedStatement.setNString(parameterIndex, value);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setNCharacterStream(int parameterIndex, Reader value, long length) {
        try {
            preparedStatement.setNCharacterStream(parameterIndex, value, length);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setNClob(int parameterIndex, NClob value) {
        try {
            preparedStatement.setNClob(parameterIndex, value);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setClob(int parameterIndex, Reader reader, long length) {
        try {
            preparedStatement.setClob(parameterIndex, reader, length);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setBlob(int parameterIndex, InputStream inputStream, long length) {
        try {
            preparedStatement.setBlob(parameterIndex, inputStream, length);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setNClob(int parameterIndex, Reader reader, long length) {
        try {
            preparedStatement.setNClob(parameterIndex, reader, length);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setSQLXML(int parameterIndex, SQLXML xmlObject) {
        try {
            preparedStatement.setSQLXML(parameterIndex, xmlObject);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) {
        try {
            preparedStatement.setObject(parameterIndex, x, targetSqlType, scaleOrLength);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setAsciiStream(int parameterIndex, InputStream x, long length) {
        try {
            preparedStatement.setAsciiStream(parameterIndex, x, length);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setBinaryStream(int parameterIndex, InputStream x, long length) {
        try {
            preparedStatement.setBinaryStream(parameterIndex, x, length);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setCharacterStream(int parameterIndex, Reader reader, long length) {
        try {
            preparedStatement.setCharacterStream(parameterIndex, reader, length);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setAsciiStream(int parameterIndex, InputStream x) {
        try {
            preparedStatement.setAsciiStream(parameterIndex, x);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setBinaryStream(int parameterIndex, InputStream x) {
        try {
            preparedStatement.setBinaryStream(parameterIndex, x);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setCharacterStream(int parameterIndex, Reader reader) {
        try {
            preparedStatement.setCharacterStream(parameterIndex, reader);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setNCharacterStream(int parameterIndex, Reader value) {
        try {
            preparedStatement.setNCharacterStream(parameterIndex, value);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setClob(int parameterIndex, Reader reader) {
        try {
            preparedStatement.setClob(parameterIndex, reader);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setBlob(int parameterIndex, InputStream inputStream) {
        try {
            preparedStatement.setBlob(parameterIndex, inputStream);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setNClob(int parameterIndex, Reader reader) {
        try {
            preparedStatement.setNClob(parameterIndex, reader);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> T unwrap(Class<T> iface) {
        try {
            if (iface.equals(PreparedStatement.class)) {
                return (T) preparedStatement;
            }
            return preparedStatement.unwrap(iface);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) {
        try {
            return iface.equals(PreparedStatement.class) || preparedStatement.isWrapperFor(iface);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }
}
